/*
 * Copyright (c) 2017-present, CV4J Contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.cv4j.image.util;

import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.Texture;
import ohos.agp.utils.Color;
import ohos.media.image.PixelMap;

import com.cv4j.core.datamodel.Point;
import com.cv4j.core.datamodel.Rect;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.StringTokenizer;
import java.util.Vector;

/**
 * 工具
 * This class contains static utility methods.
 *
 * @author dev
 * @date 2021/07/22
 */
public class Tools {
    /**
     * The value of pi as a float.
     */
    public static final float PI = (float) Math.PI;

    /**
     * The value of half pi as a float.
     */
    public static final float HALF_PI = (float) Math.PI / 2.0f;

    /**
     * The value of quarter pi as a float.
     */
    public static final float QUARTER_PI = (float) Math.PI / 4.0f;

    /**
     * The value of two pi as a float.
     */
    public static final float TWO_PI = (float) Math.PI * 2.0f;

    /**
     * This array contains the 16 hex digits '0'-'F'.
     */
    public static final char[] HEX_DIGITS =
        {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

    /**
     * 夹
     *
     * @param c1 c
     * @return int
     */
    public static int clamp(int c1) {
        return c1 > 255 ? 255 : ((c1 < 0) ? 0 : c1);
    }

    /**
     * 夹
     *
     * @param c1 c
     * @return int
     */
    public static int clamp(float c1) {
        float p1 = (c1 > 255 ? 255 : ((c1 < 0) ? 0 : c1));
        return (int) p1;
    }

    /**
     * 夹
     *
     * @param c1 c
     * @return int
     */
    public static int clamp(double c1) {
        double p1 = (c1 > 255 ? 255 : ((c1 < 0) ? 0 : c1));
        return (int) p1;
    }

    /**
     * calc手段和开发
     *
     * @param data 数据
     * @return {@link float[]}
     */
    public static float[] calcMeansAndDev(float[] data) {
        float sum = 0;
        for (int i = 0; i < data.length; i++) {
            sum += data[i];
        }
        float means = sum / data.length;
        double powSum = 0.0;
        for (int d = 0; d < data.length; d++) {
            powSum += Math.pow((data[d] - means), 2);
        }

        float sdValue = (float) Math.sqrt(powSum / (data.length - 1.0d));
        return new float[]{means, sdValue};
    }

    /**
     * calc手段和开发
     *
     * @param data 数据
     * @return {@link float[]}
     */
    public static float[] calcMeansAndDev(int[] data) {
        float sum = 0;
        for (int i = 0; i < data.length; i++) {
            sum += data[i];
        }
        float means = sum / data.length;
        double powSum = 0.0;
        for (int d = 0; d < data.length; d++) {
            powSum += Math.pow((data[d] - means), 2);
        }

        float sdValue = (float) Math.sqrt(powSum / (data.length - 1.0d));
        return new float[]{means, sdValue};
    }

    /**
     * 夹
     *
     * @param x1 x
     * @param a1 一个
     * @param b1 b
     * @return int
     */
    public static int clamp(int x1, int a1, int b1) {
        return (x1 < a1) ? a1 : (x1 > b1) ? b1 : x1;
    }

    /**
     * 双线性插值
     * Bilinear interpolation of ARGB values.
     *
     * @param x1 the X interpolation parameter 0..1
     * @param y1 y the y interpolation parameter 0..1
     * @param nwe nwe
     * @param sw sw
     * @param se se
     * @return the interpolated value
     */
    public static int bilinearInterpolate(float x1, float y1, int[] nwe, int sw, int se) {
        float m0;
        float m1;
        int nw = nwe[0];
        int a0 = (nw >> 24) & 0xff;
        int r0 = (nw >> 16) & 0xff;
        int g0 = (nw >> 8) & 0xff;
        int b0 = nw & 0xff;
        int ne = nwe[1];
        int a1 = (ne >> 24) & 0xff;
        int r1 = (ne >> 16) & 0xff;
        int g1 = (ne >> 8) & 0xff;
        int b1 = ne & 0xff;

        int a2 = (sw >> 24) & 0xff;
        int r2 = (sw >> 16) & 0xff;
        int g2 = (sw >> 8) & 0xff;
        int b2 = sw & 0xff;

        int a3 = (se >> 24) & 0xff;
        int r3 = (se >> 16) & 0xff;
        int g3 = (se >> 8) & 0xff;
        int b3 = se & 0xff;

        float cx = 1.0f - x1;
        float cy = 1.0f - y1;

        m0 = cx * a0 + x1 * a1;
        m1 = cx * a2 + x1 * a3;
        int as = (int) (cy * m0 + y1 * m1);

        m0 = cx * r0 + x1 * r1;
        m1 = cx * r2 + x1 * r3;
        int rs = (int) (cy * m0 + y1 * m1);

        m0 = cx * g0 + x1 * g1;
        m1 = cx * g2 + x1 * g3;
        int gs = (int) (cy * m0 + y1 * m1);

        m0 = cx * b0 + x1 * b1;
        m1 = cx * b2 + x1 * b3;
        int bs = (int) (cy * m0 + y1 * m1);

        return (as << 24) | (rs << 16) | (gs << 8) | bs;
    }

    /**
     * c2hex
     * Converts a Color to an 7 byte hex string starting with '#'.
     *
     * @param c1 c
     * @return String
     */
    public static String c2hex(int c1) {
        char[] buf7 = new char[7];
        buf7[0] = '#';
        for (int pos = 6; pos >= 1; pos--) {
            buf7[pos] = HEX_DIGITS[c1 & 0xf];
            c1 >>>= 4;
        }
        return new String(buf7);
    }

    /**
     * f2hex
     * Converts a float to an 9 byte hex string starting with '#'.
     *
     * @param f1 f
     * @return String
     */
    public static String f2hex(float f1) {
        int i1 = Float.floatToIntBits(f1);
        char[] buf9 = new char[9];
        buf9[0] = '#';
        for (int pos = 8; pos >= 1; pos--) {
            buf9[pos] = HEX_DIGITS[i1 & 0xf];
            i1 >>>= 4;
        }
        return new String(buf9);
    }

    /**
     * int2hex
     * Converts an int to a zero-padded hex string of fixed length 'digits'.
     * If the number is too high, it gets truncated, keeping only the lowest 'digits' characters.
     *
     * @param i1 i
     * @param digits digits
     * @return String
     */
    public static String int2hex(int i1, int digits) {
        int temp = i1;
        char[] buf = new char[digits];
        for (int pos = buf.length - 1; pos >= 0; pos--) {
            buf[pos] = HEX_DIGITS[temp & 0xf];
            temp >>>= 4;
        }
        return new String(buf);
    }

    /**
     * rgb2hsv
     *
     * @param rgb rgb
     * @param hsv hsv
     */
    public static void rgb2hsv(byte[][] rgb, byte[][] hsv) {
        byte[] red = rgb[0];
        byte[] green = rgb[1];
        byte[] blue = rgb[2];
        int len = red.length;
        for (int i1 = 0; i1 < len; i1++) {
            int r1 = red[i1] & 0xff;
            int g1 = green[i1] & 0xff;
            int b1 = blue[i1] & 0xff;
            int[] result = rgb2hsv(r1, g1, b1);
            hsv[0][i1] = (byte) result[0];
            hsv[1][i1] = (byte) result[1];
            hsv[2][i1] = (byte) result[2];
        }
    }

    /**
     * rgb2hsv
     *
     * @param r1 r
     * @param g1 g
     * @param b1 b
     * @return int[]
     */
    public static int[] rgb2hsv(int r1, int g1, int b1) {
        double delta;
        double min;
        double h1 = 0;
        double s1;
        double v1;

        min = Math.min(Math.min(r1, g1), b1);
        v1 = Math.max(Math.max(r1, g1), b1);
        delta = v1 - min;

        if (Double.doubleToLongBits(v1) == Double.doubleToLongBits(0.0)) {
            s1 = 0;
        } else {
            s1 = delta / v1;
        }

        if (s1 == 0) {
            h1 = 0.0;
        } else {
            if (r1 == v1) {
                h1 = (g1 - b1) / delta;
            } else if (g1 == v1) {
                h1 = 2 + (b1 - r1) / delta;
            } else if (b1 == v1) {
                h1 = 4 + (r1 - g1) / delta;
            } else {
                h1 = 0.0;
            }
            h1 *= 60;
            if (h1 < 0.0) {
                h1 = h1 + 360;
            }
        }
        return new int[]{(int) (h1 / 2.0), (int) (s1 * 255), (int) (v1 / 255) * 255};
    }

    /**
     * 得到最小最大
     * getMinMax
     *
     * @param a1 a
     * @return double
     */
    public static double[] getMinMax(final double[] a1) {
        double min = Double.MAX_VALUE;
        double max = -Double.MAX_VALUE;
        double value;
        for (double anA : a1) {
            value = anA;
            if (value < min) {
                min = value;
            }
            if (value > max) {
                max = value;
            }
        }
        double[] minAndMax = new double[2];
        minAndMax[0] = min;
        minAndMax[1] = max;
        return minAndMax;
    }

    /**
     * 得到最小最大
     * getMinMax
     *
     * @param a1 一个
     * @return double[]
     */
    public static double[] getMinMax(final float[] a1) {
        double min = Double.MAX_VALUE;
        double max = -Double.MAX_VALUE;
        double value;
        for (float anA : a1) {
            value = anA;
            if (value < min) {
                min = value;
            }
            if (value > max) {
                max = value;
            }
        }
        double[] minAndMax = new double[2];
        minAndMax[0] = min;
        minAndMax[1] = max;
        return minAndMax;
    }

    /**
     * 得到最小最大loc
     * 获取图中最大与最小值的位置信息， 0 - 最小值位置， 1 - 最大值位置
     *
     * @param a1 一个
     * @param width 宽度
     * @param height 高度
     * @return {@link Point[]}
     */
    public static Point[] getMinMaxLoc(final float[] a1, int width, int height) {
        double min = Double.MAX_VALUE;
        double max = -Double.MAX_VALUE;
        double value;
        int minIndex = 0;
        int maxIndex = 0;
        int index = 0;
        for (float anA : a1) {
            value = anA;
            if (value < min) {
                min = value;
                minIndex = index;
            }
            if (value > max) {
                max = value;
                maxIndex = index;
            }
            index++;
        }
        Point[] minAndMax = new Point[2];
        minAndMax[0] = new Point();
        minAndMax[0].y1 = minIndex / width;
        minAndMax[0].x1 = minIndex % width;
        minAndMax[1] = new Point();
        minAndMax[1].y1 = maxIndex / width;
        minAndMax[1].x1 = maxIndex % width;
        return minAndMax;
    }

    /**
     * 增加一倍
     * Converts the double array 'a' to a double array.
     *
     * @param a1 一个
     * @return double[]
     */
    public static double[] toDouble(float[] a1) {
        int len = a1.length;
        double[] d1 = new double[len];
        for (int i = 0; i < len; i++) {
            d1[i] = a1[i];
        }
        return d1;
    }

    /**
     * 浮动
     * Converts the float array 'a' to a double array.
     *
     * @param a1 a
     * @return float[]
     */
    public static float[] toFloat(double[] a1) {
        int len = a1.length;
        float[] f1 = new float[len];
        for (int i1 = 0; i1 < len; i1++) {
            f1[i1] = (float) a1[i1];
        }
        return f1;
    }

    /**
     * 修复新行
     * Converts carriage returns to line feeds.
     *
     * @param s1 s
     * @return String
     */
    public static Optional fixNewLines(String s1) {
        if (s1 == null) {
            return Optional.empty();
        }
        char[] chars = s1.toCharArray();
        for (int i1 = 0; i1 < chars.length; i1++) {
            if (chars[i1] == '\r') {
                chars[i1] = '\n';
            }
        }
        return Optional.ofNullable(new String(chars));
    }

    /**
     * 解析双
     * Returns a double containg the value represented by the
     * specified <code>String</code>.
     *
     * @param s1 the string to be parsed.
     * @param defaultValue the value returned if <code>s</code>
     * does not contain a parsable double
     * @return The double value represented by the string argument or
     * <code>defaultValue</code> if the string does not contain a parsable double
     */
    public static double parseDouble(String s1, double defaultValue) {
        if (s1 == null) {
            return defaultValue;
        }
        try {
            return Double.parseDouble(s1);
        } catch (NumberFormatException e) {
            return defaultValue;
        }
    }

    /**
     * 解析双
     * Returns a double containg the value represented by the
     * specified <code>String</code>.
     *
     * @param s1 the string to be parsed.
     * @return The double value represented by the string argument or
     * Double.NaN if the string does not contain a parsable double
     */
    public static double parseDouble(String s1) {
        return parseDouble(s1, Double.NaN);
    }

    /**
     * 小数点后得到
     * Returns the number of decimal places needed to display a
     * number, or -2 if exponential notation should be used.
     *
     * @param n1 n
     * @return the number
     */
    public static int getDecimalPlaces(double n1) {
        if ((int) n1 == n1 || Double.isNaN(n1)) {
            return 0;
        }
        String s1 = "" + n1;
        if (s1.contains("E")) {
            return -2;
        }
        while (s1.endsWith("0")) {
            s1 = s1.substring(0, s1.length() - 1);
        }
        int index = s1.indexOf(".");
        if (index == -1) {
            return 0;
        }
        int digits = s1.length() - index - 1;
        if (digits > 4) {
            digits = 4;
        }
        return digits;
    }


    /**
     * 小数点后得到
     * Returns the number of decimal places needed to display two numbers,
     * or -2 if exponential notation should be used.
     *
     * @param n1 n1
     * @param n2 n2
     * @return the number
     */
    public static int getDecimalPlaces(double n1, double n2) {
        if ((int) n1 == n1 && (int) n2 == n2) {
            return 0;
        }
        int digits = getDecimalPlaces(n1);
        int digits2 = getDecimalPlaces(n2);
        if (digits == 0) {
            return digits2;
        }
        if (digits2 == 0) {
            return digits;
        }
        if (digits < 0 || digits2 < 0) {
            return digits;
        }
        if (digits2 > digits) {
            digits = digits2;
        }
        return digits;
    }

    /**
     * 分裂
     * Splits a string into substrings using the default delimiter set,
     * which is " \t\n\r" (space, tab, newline and carriage-return).
     *
     * @param str str
     * @return String[]
     */
    public static String[] split(String str) {
        return split(str, " \t\n\r");
    }

    /**
     * 分裂
     * Splits a string into substring using the characters
     * contained in the second argument as the delimiter set.
     *
     * @param str str
     * @param delim delim
     * @return String[]
     */
    public static String[] split(String str, String delim) {
        if ("\n".equals(delim)) {
            return splitLines(str);
        }
        StringTokenizer t1 = new StringTokenizer(str, delim);
        int tokens = t1.countTokens();
        String[] strings;
        if (tokens > 0) {
            strings = new String[tokens];
            for (int i1 = 0; i1 < tokens; i1++) {
                strings[i1] = t1.nextToken();
            }
        } else {
            strings = new String[0];
        }
        return strings;
    }

    /**
     * 分线
     * Splits a string into substring using the characters
     * contained in the second argument as the delimiter set.
     *
     * @param str str
     * @return String[]
     */
    static String[] splitLines(String str) {
        Vector v1 = new Vector();
        BufferedReader br = null;
        try {
            br = new BufferedReader(new StringReader(str));
            String line;
            while (true) {
                line = br.readLine();
                if (line == null) {
                    break;
                }
                v1.addElement(line);
            }
        } catch (IOException e1) {
            e1.printStackTrace();
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
        }
        String[] lines = new String[v1.size()];
        v1.copyInto((String[]) lines);
        return lines;
    }

    /**
     * 排名
     * Returns a sorted list of indices of the specified double array.
     * Modified from: http://stackoverflow.com/questions/951848 by N.Vischer.
     *
     * @param values values
     * @return int[]
     */
    public static int[] rank(double[] values) {
        int n2 = values.length;
        final Integer[] indexes = new Integer[n2];
        final Double[] data = new Double[n2];
        for (int i1 = 0; i1 < n2; i1++) {
            indexes[i1] = new Integer(i1);
            data[i1] = new Double(values[i1]);
        }
        Arrays.sort(indexes, new Comparator<Integer>() {
            /**
             * compare
             * @param o1 o1
             * @param o2 o2
             * @return int
             */
            public int compare(final Integer o1, final Integer o2) {
                return data[o1].compareTo(data[o2]);
            }
        });
        int[] indexes2 = new int[n2];
        for (int i1 = 0; i1 < n2; i1++) {
            indexes2[i1] = indexes[i1].intValue();
        }
        return indexes2;
    }

    /**
     * 排名
     * Returns a sorted list of indices of the specified String array.
     *
     * @param data data
     * @return int[]
     */
    public static int[] rank(final String[] data) {
        int ns = data.length;
        final Integer[] indexes = new Integer[ns];
        for (int is = 0; is < ns; is++) {
            indexes[is] = new Integer(is);
        }
        Arrays.sort(indexes, new Comparator<Integer>() {
            /**
             * compare
             * @param o1 o1
             * @param o2 o2
             * @return int
             */
            public int compare(final Integer o1, final Integer o2) {
                return data[o1].compareToIgnoreCase(data[o2]);
            }
        });
        int[] indexes2 = new int[ns];
        for (int i = 0; i < ns; i++) {
            indexes2[i] = indexes[i].intValue();
        }
        return indexes2;
    }

    /**
     * 画矩形
     * drawRects
     *
     * @param bitmap bitmap
     * @param rectangles rectangles
     */
    public static void drawRects(PixelMap bitmap, List<Rect> rectangles) {
        Texture texture = new Texture(bitmap);
        Canvas canvas = new Canvas(texture);
        Paint paint = new Paint();
        paint.setStyle(Paint.Style.STROKE_STYLE);
        paint.setColor(Color.RED);
        for (Rect rect : rectangles) {
            canvas.drawRect(rect.x1, rect.y1, rect.br().x1, rect.br().y1, paint);
        }
    }

    /**
     * rgb到ycr cb
     * rgbToYcrCb
     *
     * @param tr tr
     * @param tg tg
     * @param tb tb
     * @return int[]
     */
    public static int[] rgbToYcrCb(int tr, int tg, int tb) {
        double sum = tr + tg + tb;
        double r1 = ((double) tr) / sum;
        double g1 = ((double) tg) / sum;
        double b1 = ((double) tb) / sum;
        double y1 = 65.481 * r1 + 128.553 * g1 + 24.966 * b1 + 16.0d;
        double cr = -37.7745 * r1 - 74.1592 * g1 + 111.9337 * b1 + 128.0d;
        double cb = 111.9581 * r1 - 93.7509 * g1 - 18.2072 * b1 + 128.0d;
        return new int[]{(int) y1, (int) cr, (int) cb};
    }
}
